/************************************
 * Package: RGcpp					*
 * Authors: Rui Eduardo Gouveia Gil	*
 * Date: 2010						*
 ************************************/
#include "rgcpp/utils/Profiler.h"

namespace rgcpp
{

Profiler::~Profiler()
{
	if(this->running)
		this->stopWriteToFile();
}

double Profiler::getTotalProfileTime()
{
	return Profiler::convertTimeToSeconds(Profiler::calculateTimeInterval(stime,Profiler::getCurrentTime()));
}

void Profiler::regThreadTime(const std::string& thread)
{
	boost::mutex::scoped_lock lock(mutThread);

	threadTime[thread] = Types<double,double>(0,0);
}

void Profiler::beginThreadTime(const std::string& thread)
{
	boost::mutex::scoped_lock lock(mutThread);

	if(threadTimeAux.find(thread) != threadTimeAux.end())
		std::cout << "Profiler - beginThreadTime problem with thread time profiling." << std::endl;

	threadTimeAux[thread] = Profiler::getCurrentTime();
}

Types<double,double,double> Profiler::getThreadTime(const std::string& thread)
{
	boost::mutex::scoped_lock lock(mutThread);

	if(threadTime.find(thread) == threadTime.end())
		return Types<double,double,double>(0,0,0);

	Types<double,double> data = threadTime[thread];

	return Types<double,double,double>(data.t1,data.t2,(data.t1/this->threadsTimeTotal)*100);
}

std::vector<Types<std::string,double,double,double> > Profiler::getThreadsTime()
{
	boost::mutex::scoped_lock lock(mutThread);

	std::vector<Types<std::string,double,double,double> > result;
	for(Map<std::string,Types<double,double> >::iterator i=threadTime.begin(); i!=threadTime.end(); i++)
		result.push_back(Types<std::string,double,double,double>(i->first,i->second.t1,i->second.t2,(i->second.t1/this->threadsTimeTotal)*100));

	return result;
}

double Profiler::getThreadsTimeTotal()
{
	boost::mutex::scoped_lock lock(mutThread);

	return this->threadsTimeTotal;
}

void Profiler::endThreadTime(const std::string& thread)
{
	boost::mutex::scoped_lock lock(mutThread);

	if(threadTimeAux.find(thread) == threadTimeAux.end())
	{
		std::cout << "Profiler - beginThreadTime problem with thread time profiling." << std::endl;
		return;
	}

	double interval = Profiler::convertTimeToSeconds(Profiler::calculateTimeInterval(threadTimeAux[thread],Profiler::getCurrentTime()));

	Types<double,double> value;
	if(threadTime.find(thread) == threadTime.end())
		value = Types<double,double>(0,0);
	else
		value = threadTime[thread];

	value.t1 += interval;
	if(value.t2 < interval)
		value.t2 = interval;

	this->threadsTimeTotal += interval;
	threadTime[thread] = value;
	threadTimeAux.erase(thread);
}

void Profiler::regModuleTime(const std::string& module)
{
	boost::mutex::scoped_lock lock(mutModule);

	moduleTime[module] = Types<double,unsigned int,double>(0,0,0);
}

void Profiler::beginModuleTime(const std::string& module)
{
	boost::mutex::scoped_lock lock(mutModule);

	if(moduleTimeAux.find(module) != moduleTimeAux.end())
		std::cout << "Profiler - beginModuleTime problem with module time profiling." << std::endl;

	moduleTimeAux[module] = Profiler::getCurrentTime();
}

Types<double,unsigned int,double,double,double> Profiler::getModuleTime(const std::string& module)
{
	boost::mutex::scoped_lock lock(mutModule);

	if(moduleTime.find(module) == moduleTime.end())
		return Types<double,unsigned int,double,double,double>(0,0,0,0,0);

	Types<double,unsigned int,double> data = moduleTime[module];

	return Types<double,unsigned int,double,double,double>(data.t1,data.t2,data.t3,data.t1/data.t2,(data.t1/this->modulesTimeTotal)*100);
}

std::vector<Types<std::string,double,unsigned int,double,double,double> > Profiler::getModulesTime()
{
	boost::mutex::scoped_lock lock(mutModule);

	std::vector<Types<std::string,double,unsigned int,double,double,double> > result;
	for(Map<std::string,Types<double,unsigned int,double> >::iterator i=moduleTime.begin(); i!=moduleTime.end(); i++)
		result.push_back(Types<std::string,double,unsigned int,double,double,double>(i->first,i->second.t1,i->second.t2,i->second.t3,i->second.t1/i->second.t2,(i->second.t1/this->modulesTimeTotal)*100));

	return result;
}

double Profiler::getModulesTimeTotal()
{
	boost::mutex::scoped_lock lock(mutModule);

	return this->modulesTimeTotal;
}

void Profiler::endModuleTime(const std::string& module)
{
	boost::mutex::scoped_lock lock(mutModule);

	if(moduleTimeAux.find(module) == moduleTimeAux.end())
	{
		std::cout << "Profiler - endModuleTime problem with module time profiling." << std::endl;
		return;
	}

	double interval = Profiler::convertTimeToSeconds(Profiler::calculateTimeInterval(moduleTimeAux[module],Profiler::getCurrentTime()));

	Types<double,unsigned int,double> value;
	if(moduleTime.find(module) == moduleTime.end())
		value = Types<double,unsigned int,double>(0,0,0);
	else
		value = moduleTime[module];


	value.t1 += interval;
	value.t2++;
	if(value.t3 < interval)
		value.t3 = interval;

	this->modulesTimeTotal += interval;
	moduleTime[module] = value;
	moduleTimeAux.erase(module);
}

void Profiler::print()
{
	std::cout << this->generateString() << std::endl;
}

std::string Profiler::generateString()
{
	std::stringstream stream;

	stream << "Profile Time = " << this->getTotalProfileTime() << " seconds" << std::endl;

	stream << std::endl;
	stream << std::endl;

	//thread,total,maxtime,percentage
	std::vector<Types<std::string,double,double,double> > threads = this->getThreadsTime();
	for(std::vector<Types<std::string,double,double,double> >::iterator i=threads.begin(); i!=threads.end(); i++)
	{
		stream << "Thread [" << i->t1 << "]" << std::endl;
		stream << "Total Time = " << i->t2 << " seconds" << std::endl;
		stream << "Max Time = " << i->t3 << " seconds" << std::endl;
		stream << "Percentage = " << i->t4 << "%" << std::endl;

		stream << std::endl;
	}

	if(!threads.empty())
		stream << std::endl;

	//module,total,count,maxtime,average,percentage
	std::vector<Types<std::string,double,unsigned int,double,double,double> > modules = this->getModulesTime();
	for(std::vector<Types<std::string,double,unsigned int,double,double,double> >::iterator i=modules.begin(); i!=modules.end(); i++)
	{
		stream << "Module [" << i->t1 << "]" << std::endl;
		stream << "Total Time = " << i->t2 << " seconds" << std::endl;
		stream << "Count = " << i->t3 << std::endl;
		stream << "Max Time = " << i->t4 << " seconds" << std::endl;
		stream << "Average = " << i->t5 << " seconds" << std::endl;
		stream << "Percentage = " << i->t6 << "%" << std::endl;

		stream << std::endl;
	}

	if(!modules.empty())
		stream << std::endl;

	std::string result = stream.str();
	return result;
}

void Profiler::writeToFile(const std::string& filePath)
{
	std::ofstream file(filePath.c_str(),std::ios_base::binary | std::ios_base::out);

	if(!file.good())
	{
		std::cout << "Profiler: Invalid file path!!!" << std::endl;
		return;
	}

	std::string str = this->generateString();
	file.write(str.c_str(),str.size());

	file.close();
}

void Profiler::startWriteToFile(const std::string& filePath, double seconds)
{
	this->running=true;
	this->seconds=seconds;
	this->filePath=filePath;
	boost::mutex::scoped_lock lock(initMutex);
	mThread = new boost::thread(boost::ref(*this));
}

void Profiler::stopWriteToFile()
{
	if(!this->running)
		return;

	this->running=false;
	mThread->interrupt();
	mThread->join();
	delete mThread;
}

void Profiler::operator()()
{
	boost::mutex::scoped_lock lock(initMutex);

	boost::xtime stime;
	unsigned long sec = seconds;
	unsigned long nsec = ((seconds-(double)sec)*NANOSECONDS_PER_SECOND);

	std::ofstream file(filePath.c_str(),std::ios_base::binary | std::ios_base::out);

	if(!file.good())
	{
		std::cout << "Profiler: Invalid file path!!!" << std::endl;
		return;
	}

	Map<std::string,unsigned long> oldValues;

	std::vector<Types<std::string,double,double,double> > threads = this->getThreadsTime();
	for(std::vector<Types<std::string,double,double,double> >::iterator i=threads.begin(); i!=threads.end(); i++)
	{
		file << "\"" << i->t1 << "\",";
		oldValues[i->t1] = 0;
	}

	std::vector<Types<std::string,double,unsigned int,double,double,double> > modules = this->getModulesTime();
	for(std::vector<Types<std::string,double,unsigned int,double,double,double> >::iterator i=modules.begin(); i!=modules.end(); i++)
	{
		file << "\"" << i->t1 << "\",";
		oldValues[i->t1] = 0;
	}

	try
	{
		unsigned long value;
		while(this->running)
		{
			stime = Profiler::getCurrentTime();
			stime.sec += sec;
			stime.nsec += nsec;

			boost::thread::sleep(stime);

			std::vector<Types<std::string,double,double,double> > threads = this->getThreadsTime();
			for(std::vector<Types<std::string,double,double,double> >::iterator i=threads.begin(); i!=threads.end(); i++)
			{
				value = ((((unsigned long)(i->t2))*1000) + ((unsigned long)(((i->t2)-(((unsigned long)(i->t2))))*1000)));
				file << (value-oldValues[i->t1]) << ",";
				oldValues[i->t1] = value;
			}

			std::vector<Types<std::string,double,unsigned int,double,double,double> > modules = this->getModulesTime();
			for(std::vector<Types<std::string,double,unsigned int,double,double,double> >::iterator i=modules.begin(); i!=modules.end(); i++)
			{
				value = ((((unsigned long)(i->t2))*1000) + ((unsigned long)(((i->t2)-(((unsigned long)(i->t2))))*1000)));
				file << (value-oldValues[i->t1]) << ",";
				oldValues[i->t1] = value;
			}
		}
	}
	catch(boost::thread_interrupted& e)
	{

	}

	file.close();
}

boost::xtime Profiler::getCurrentTime()
{
	boost::xtime rtime;
	boost::xtime_get(&rtime, boost::TIME_UTC);
	return rtime;
}

boost::xtime Profiler::calculateTimeInterval(boost::xtime stime, boost::xtime etime)
{
	boost::xtime rtime;

	rtime.sec = etime.sec - stime.sec;

	if(stime.nsec > etime.nsec)
	{
		rtime.sec--;
		rtime.nsec = etime.nsec + (NANOSECONDS_PER_SECOND - stime.nsec);
	}
	else
		rtime.nsec = etime.nsec - stime.nsec;

	return rtime;
}

void Profiler::appendTimeInterval(boost::xtime stime, boost::xtime etime, boost::xtime& rtime)
{
	boost::xtime atime = Profiler::calculateTimeInterval(stime,etime);

	Profiler::appendTime(rtime,atime);
}

double Profiler::convertTimeToSeconds(boost::xtime rtime)
{
	return ((double)rtime.sec)+(((double)rtime.nsec)/((double)NANOSECONDS_PER_SECOND));
}

unsigned long Profiler::convertTimeToMilliseconds(boost::xtime rtime)
{
	return (((unsigned long)rtime.sec%1000000)*1000)+((unsigned long)(rtime.nsec/1000000));
}

boost::xtime Profiler::convertTimeToXTime(double time)
{
	boost::xtime rtime;
	rtime.sec = time;
	rtime.nsec = ((time-(double)rtime.sec)*NANOSECONDS_PER_SECOND);

	return rtime;
}

boost::xtime Profiler::convertTimeToXTime(unsigned long time)
{
	boost::xtime rtime;
	rtime.sec = time/1000;
	rtime.nsec = (time%1000)*1000000;

	return rtime;
}

boost::xtime Profiler::appendTime(boost::xtime& rtime, boost::xtime atime)
{
	rtime.sec += atime.sec;
	rtime.nsec += atime.nsec;

	if(rtime.nsec > NANOSECONDS_PER_SECOND)
	{
		rtime.sec++;
		rtime.nsec -= NANOSECONDS_PER_SECOND;
	}

	return rtime;
}

boost::xtime Profiler::appendTimeInSeconds(boost::xtime& rtime, double atime)
{
	boost::xtime stime;
	stime.sec = atime;
	stime.nsec = ((atime-(double)stime.sec)*NANOSECONDS_PER_SECOND);

	return Profiler::appendTime(rtime,stime);
}

Profiler::Profiler()
{
	this->running=false;
	this->threadsTimeTotal=0;
	this->modulesTimeTotal=0;

	boost::xtime_get(&stime,boost::TIME_UTC);
}

}
